package com.yanyeori.framework.core.util;

import org.springframework.util.StreamUtils;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 摘要签名工具类
 *
 * @author chenkuan 2019/3/22
 */
public class DigestUtil {

    public static String md5(String input) {
        return Digest.build(Digest.Type.MD5).digest(input.getBytes(StandardCharsets.UTF_8)).toHex();
    }

    public static String md5(InputStream input) {
        return Digest.build(Digest.Type.MD5).digest(input).toHex();
    }

    public static String md5(File input) {
        return Digest.build(Digest.Type.MD5).digest(input).toHex();
    }

    public static String sha1(String input) {
        return Digest.build(Digest.Type.SHA1).digest(input.getBytes(StandardCharsets.UTF_8)).toHex();
    }

    public static String sha1(InputStream input) {
        return Digest.build(Digest.Type.SHA1).digest(input).toHex();
    }

    public static String sha1(File input) {
        return Digest.build(Digest.Type.SHA1).digest(input).toHex();
    }

    public static String sha256(String input) {
        return Digest.build(Digest.Type.SHA256).digest(input.getBytes(StandardCharsets.UTF_8)).toHex();
    }

    public static String sha256(InputStream input) {
        return Digest.build(Digest.Type.SHA256).digest(input).toHex();
    }

    public static String sha256(File input) {
        return Digest.build(Digest.Type.SHA256).digest(input).toHex();
    }

    public static String sha512(String input) {
        return Digest.build(Digest.Type.SHA512).digest(input.getBytes(StandardCharsets.UTF_8)).toHex();
    }

    public static String sha512(InputStream input) {
        return Digest.build(Digest.Type.SHA512).digest(input).toHex();
    }

    public static String sha512(File input) {
        return Digest.build(Digest.Type.SHA512).digest(input).toHex();
    }

    public static Result hmacMd5(String key, String input) {
        return DigestHmac.build(DigestHmac.Type.HMAC_MD5, key).digest(input.getBytes(StandardCharsets.UTF_8));
    }

    public static Result hmacMd5(String key, InputStream input) {
        return DigestHmac.build(DigestHmac.Type.HMAC_MD5, key).digest(input);
    }

    public static Result hmacMd5(String key, File input) {
        return DigestHmac.build(DigestHmac.Type.HMAC_MD5, key).digest(input);
    }

    public static Result hmacSha1(String key, String input) {
        return DigestHmac.build(DigestHmac.Type.HMAC_SHA1, key).digest(input.getBytes(StandardCharsets.UTF_8));
    }

    public static Result hmacSha1(String key, InputStream input) {
        return DigestHmac.build(DigestHmac.Type.HMAC_SHA1, key).digest(input);
    }

    public static Result hmacSha1(String key, File input) {
        return DigestHmac.build(DigestHmac.Type.HMAC_SHA1, key).digest(input);
    }

    public static Result hmacSha256(String key, String input) {
        return DigestHmac.build(DigestHmac.Type.HMAC_SHA256, key).digest(input.getBytes(StandardCharsets.UTF_8));
    }

    public static Result hmacSha256(String key, InputStream input) {
        return DigestHmac.build(DigestHmac.Type.HMAC_SHA256, key).digest(input);
    }

    public static Result hmacSha256(String key, File input) {
        return DigestHmac.build(DigestHmac.Type.HMAC_SHA256, key).digest(input);
    }

    public static Result hmacSha512(String key, String input) {
        return DigestHmac.build(DigestHmac.Type.HMAC_SHA512, key).digest(input.getBytes(StandardCharsets.UTF_8));
    }

    public static Result hmacSha512(String key, InputStream input) {
        return DigestHmac.build(DigestHmac.Type.HMAC_SHA512, key).digest(input);
    }

    public static Result hmacSha512(String key, File input) {
        return DigestHmac.build(DigestHmac.Type.HMAC_SHA512, key).digest(input);
    }

    public static class Digest {
        public enum Type {
            MD5("MD5"),
            SHA1("SHA1"),
            SHA256("SHA256"),
            SHA512("SHA512");

            private final String algorithm;

            Type(String algorithm) {
                this.algorithm = algorithm;
            }
        }

        private final Type type;

        private Digest(Type type) {
            this.type = type;
        }

        public static Digest build(Type type) {
            return new Digest(type);
        }

        public Result digest(byte[] input) {
            try {
                byte[] bytes = MessageDigest.getInstance(type.algorithm).digest(input);
                return new Result(bytes);
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }
        }

        public Result digest(InputStream input) {
            try {
                MessageDigest md = MessageDigest.getInstance(type.algorithm);
                byte[] buffer = new byte[StreamUtils.BUFFER_SIZE];
                int read;
                while ((read = input.read(buffer)) > -1) {
                    md.update(buffer, 0, read);
                }
                byte[] bytes = md.digest();
                return new Result(bytes);
            } catch (NoSuchAlgorithmException | IOException e) {
                throw new RuntimeException(e);
            }
        }

        public Result digest(File input) {
            FileInputStream in = null;
            try {
                in = new FileInputStream(input);
                return digest(in);
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            } finally {
                FileUtil.close(in);
            }
        }
    }

    public static class DigestHmac {
        public enum Type {
            HMAC_MD5("HmacMD5"),
            HMAC_SHA1("HmacSHA1"),
            HMAC_SHA256("HmacSHA256"),
            HMAC_SHA512("HmacSHA512");

            private final String algorithm;

            Type(String algorithm) {
                this.algorithm = algorithm;
            }
        }

        private final Type type;
        private final byte[] key;

        private DigestHmac(Type type, byte[] key) {
            this.type = type;
            this.key = key;
        }

        public static DigestHmac build(Type type, byte[] key) {
            return new DigestHmac(type, key);
        }

        public static DigestHmac build(Type type, String key) {
            return new DigestHmac(type, key.getBytes(StandardCharsets.UTF_8));
        }

        public Result digest(byte[] input) {
            try {
                SecretKey secretKey = new SecretKeySpec(key, type.algorithm);
                Mac mac = Mac.getInstance(type.algorithm);
                mac.init(secretKey);
                byte[] bytes = mac.doFinal(input);
                return new Result(bytes);
            } catch (NoSuchAlgorithmException | InvalidKeyException e) {
                throw new RuntimeException(e);
            }
        }

        public Result digest(InputStream input) {
            try {
                SecretKey secretKey = new SecretKeySpec(key, type.algorithm);
                Mac mac = Mac.getInstance(type.algorithm);
                mac.init(secretKey);
                byte[] buffer = new byte[StreamUtils.BUFFER_SIZE];
                int read;
                while ((read = input.read(buffer)) > -1) {
                    mac.update(buffer, 0, read);
                }
                byte[] bytes = mac.doFinal();
                return new Result(bytes);
            } catch (NoSuchAlgorithmException | InvalidKeyException | IOException e) {
                throw new RuntimeException(e);
            }
        }

        public Result digest(File input) {
            FileInputStream in = null;
            try {
                in = new FileInputStream(input);
                return digest(in);
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            } finally {
                FileUtil.close(in);
            }
        }
    }

    public static class Result {
        private final byte[] bytes;

        private Result(byte[] bytes) {
            this.bytes = bytes;
        }

        public byte[] getBytes() {
            return bytes;
        }

        public String toHex() {
            return StringUtil.toHex(bytes);
        }

        public String toBase64() {
            return StringUtil.toBase64(bytes);
        }

        @Override
        public String toString() {
            return toHex();
        }
    }
}
